﻿using System;
using System.Collections;
using System.Reflection;
using System.Security;
using System.Security.Permissions;
using System.Security.Policy;

namespace AzManPermissions
{
  /// <summary>
  /// Abstract base class for creating AzMan operation checks against the active principal (see <see cref="T:System.Security.Principal.IPrincipal" />) using the language constructs defined for both declarative and imperative security actions.
  /// </summary>
  public abstract class AzManBasePermission : IPermission
  {
    /// <summary>
    /// Descriptors for the AzMan operations to check.
    /// </summary>
    internal PermissionDescriptor[] descriptors;

    #region Constructors

    /// <summary>
    /// Initializes a new instance of the <see cref="T:AzManPermissions.AzManBasePermission" /> class for the specified <paramref name="name" /> and <paramref name="operation" />.
    /// </summary>
    /// <param name="name">The name of the <see cref="T:System.Security.Principal.IPrincipal" /> object's user.</param>
    /// <param name="operation">The AzMan operation number to check for.</param>
    public AzManBasePermission(string name, int operation)
    {
      SetParams(name, operation);
      Initialize();
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="T:AzManPermissions.AzManBasePermission" /> class for the specified <paramref name="name" /> and <paramref name="operation" /> and <paramref name="scopeName" />.
    /// </summary>
    /// <param name="name">The name of the <see cref="T:System.Security.Principal.IPrincipal" /> object's user.</param>
    /// <param name="operation">The AzMan operation number to check for.</param>
    /// <param name="scopeName">The name of a scope as defined in the AzMan store.</param>
    public AzManBasePermission(string name, int operation, string scopeName)
    {
      SetParams(name, operation, scopeName);
      Initialize();
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="T:AzManPermissions.AzManBasePermission" /> class for the specified <paramref name="name" /> and <paramref name="operation" /> and <paramref name="scopeName" /> and <paramref name="objectName" />.
    /// </summary>
    /// <param name="name">The name of the <see cref="T:System.Security.Principal.IPrincipal" /> object's user.</param>
    /// <param name="operation">The AzMan operation number to check for.</param>
    /// <param name="scopeName">The name of a scope as defined in the AzMan store.</param>
    /// <param name="objectName">The string used for AzMan run-time auditing.</param>
    public AzManBasePermission(string name, int operation, string scopeName, string objectName)
    {
      SetParams(name, operation, scopeName, objectName);
      Initialize();
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="T:AzManPermissions.AzManBasePermission" /> class with the specified <paramref name="descriptors" />.
    /// </summary>
    /// <param name="descriptors">The AzMan operation descriptors for the permission.</param>
    internal AzManBasePermission(PermissionDescriptor[] descriptors)
    {
      this.descriptors = descriptors;
      Initialize();
    }

    #endregion Constructors

    /// <summary>
    /// Allows initialization of the descendant permission classes.
    /// </summary>
    internal abstract void Initialize();

    /// <summary>
    /// Descendant classes must implement this method to provide the concrete checking algorithm based on the <paramref name="descriptor" />.
    /// </summary>
    /// <param name="descriptor">Description of the AzMan operation to check.</param>
    /// <returns>True, if the <see cref="T:System.Security.Principal.IPrincipal" /> object's user is allowed to perform the described operation.</returns>
    internal abstract bool AccessCheck(PermissionDescriptor descriptor);

    /// <summary>
    /// Descendant classes must implement this method to create a new permission instances from a descriptor array.
    /// </summary>
    /// <param name="descriptors">The AzMan operation descriptors for the permission.</param>
    /// <returns>The initialized instance of the descendant class.</returns>
    internal abstract AzManBasePermission CreatePermission(PermissionDescriptor[] descriptors);

    #region IPermission implementation

    /// <summary>
    /// Creates and returns an identical copy of the current permission.
    /// </summary>
    /// <returns>A copy of the current permission.</returns>
    public abstract IPermission Copy();

    /// <summary>
    /// Determines at run time whether the current principal is allowed to perform the operation specified by the current permission.
    /// </summary>
    /// <exception cref="T:System.Security.SecurityException">The current principal does not pass the security check for the AzMan operation specified by the current permission.</exception>
    [SecuritySafeCritical]
    public void Demand()
    {
      new SecurityPermission(SecurityPermissionFlag.ControlPrincipal).Assert();
      if (descriptors == null)
        return;
      bool flag = false;
      foreach (PermissionDescriptor descriptor in descriptors)
      {
        flag = AccessCheck(descriptor);
      }
      if (!flag)
      {
        ThrowSecurityException();
      }
    }

    /// <summary>
    /// Not yet implemented. Creates and returns a permission that is the intersection of the current permission and the specified permission.
    /// </summary>
    /// <param name="target">A permission to intersect with the current permission. It must be of the same type as the current permission.</param>
    /// <returns>A new permission that represents the intersection of the current permission and the specified permission. This new permission will be null if the intersection is empty.</returns>
    /// <exception cref="T:System.ArgumentException">The <paramref name="target" /> parameter is not null and is not an instance of the same class as the current permission.</exception>
    public IPermission Intersect(IPermission target)
    {
      throw new NotImplementedException();
    }

    /// <summary>
    /// Determines whether the current permission is a subset of the specified permission.
    /// </summary>
    /// <param name="target">A permission that is to be tested for the subset relationship. This permission must be of the same type as the current permission.</param>
    /// <returns>true if the current permission is a subset of the specified permission; otherwise, false.</returns>
    /// <exception cref="T:System.ArgumentException">The <paramref name="target" /> parameter is an object that is not of the same type as the current permission. </exception>
    public bool IsSubsetOf(IPermission target)
    {
      if (target == null)
      {
        return IsEmpty();
      }
      if (!VerifyType(target))
      {
        throw new ArgumentException(string.Format(AzManPermissions.Properties.Resources.Argument_WrongType, GetType().FullName, "target"));
      }
      bool result = false;
      AzManBasePermission azManBasePermission = (AzManBasePermission)target;
      for (int i = 0; i < this.descriptors.Length; i++)
      {
        bool flag = false;
        for (int j = 0; j < azManBasePermission.descriptors.Length; j++)
        {
          if (azManBasePermission.descriptors[j].name == this.descriptors[i].name &&
            azManBasePermission.descriptors[j].operation == this.descriptors[i].operation &&
            (azManBasePermission.descriptors[j].objectName == null || (this.descriptors[i].objectName != null && this.descriptors[i].objectName.Equals(azManBasePermission.descriptors[j].objectName))) &&
            (azManBasePermission.descriptors[j].scopeName == null || (this.descriptors[i].scopeName != null && this.descriptors[i].scopeName.Equals(azManBasePermission.descriptors[j].scopeName))))
          {
            flag = true;
            break;
          }
        }
        if (!flag)
        {
          result = false;
          return result;
        }
      }
      result = true;
      return result;
    }

    /// <summary>
    /// Creates a permission that is the union of the current permission and the specified permission.
    /// </summary>
    /// <param name="target">A permission to combine with the current permission. It must be of the same type as the current permission.</param>
    /// <returns>A new permission that represents the union of the current permission and the specified permission.</returns>
    /// <exception cref="T:System.ArgumentException">The <paramref name="other" /> parameter is an object that is not of the same type as the current permission. </exception>
    public IPermission Union(IPermission target)
    {
      if (target == null)
      {
        return Copy();
      }
      if (!VerifyType(target))
      {
        throw new ArgumentException(string.Format(AzManPermissions.Properties.Resources.Argument_WrongType, GetType().FullName, "target"));
      }
      AzManBasePermission azManBasePermission = (AzManBasePermission)target;
      int num = this.descriptors.Length + azManBasePermission.descriptors.Length;
      PermissionDescriptor[] array = new PermissionDescriptor[num];
      int i;
      for (i = 0; i < this.descriptors.Length; i++)
      {
        array[i] = this.descriptors[i];
      }
      for (int j = 0; j < azManBasePermission.descriptors.Length; j++)
      {
        array[i + j] = azManBasePermission.descriptors[j];
      }
      return CreatePermission(array);
    }

    /// <summary>
    /// Reconstructs a permission with a specified state from an XML encoding.
    /// </summary>
    /// <param name="elem">The XML encoding to use to reconstruct the permission.</param>
    /// <exception cref="T:System.ArgumentNullException">The <paramref name="elem" /> parameter is null.</exception>
    /// <exception cref="T:System.ArgumentException">The <paramref name="elem" /> parameter is not a valid permission element.-or- The <paramref name="elem" /> parameter's version number is not valid.</exception>
    public void FromXml(SecurityElement elem)
    {
      //CodeAccessPermission.ValidateElement(elem, this);
      if (elem.Children != null && elem.Children.Count != 0)
      {
        int count = elem.Children.Count;
        int num = 0;
        descriptors = new PermissionDescriptor[count];
        IEnumerator enumerator = elem.Children.GetEnumerator();
        while (enumerator.MoveNext())
        {
          PermissionDescriptor descriptor = new PermissionDescriptor();
          descriptor.FromXml((SecurityElement)enumerator.Current);
          descriptors[num++] = descriptor;
        }
        return;
      }
      descriptors = new PermissionDescriptor[0];
    }

    /// <summary>
    /// Creates an XML encoding of the permission and its current state.
    /// </summary>
    /// <returns>An XML encoding of the permission, including any state information.</returns>
    public SecurityElement ToXml()
    {
      SecurityElement securityElement = new SecurityElement("IPermission");
      AddClassAttribute(securityElement, GetType());
      securityElement.AddAttribute("version", "1");
      int num = this.descriptors.Length;
      for (int i = 0; i < num; i++)
      {
        securityElement.AddChild(this.descriptors[i].ToXml());
      }
      return securityElement;
    }

    #endregion IPermission implementation

    #region Internal and private helper methods

    /// <summary>
    /// Throws a <see cref="T:System.Security.SecurityException" /> exception.
    /// </summary>
    [SecurityCritical]
    internal void ThrowSecurityException()
    {
      AssemblyName assemblyName = null;
      Evidence evidence = null;
      //PermissionSet.s_fullTrust.Assert();
      try
      {
        Assembly callingAssembly = Assembly.GetCallingAssembly();
        assemblyName = callingAssembly.GetName();
        if (callingAssembly != Assembly.GetExecutingAssembly())
        {
          evidence = callingAssembly.Evidence;
        }
      }
      catch
      {
      }
      //PermissionSet.RevertAssert();
      throw new SecurityException(AzManPermissions.Properties.Resources.Security_AzManPermission, assemblyName, null, null, null, SecurityAction.Demand, this, this, evidence);
    }

    /// <summary>
    /// Checks if the current permission is empty.
    /// </summary>
    /// <returns>true, if all AzMan operation descriptor is empty; otherwise, false.</returns>
    internal bool IsEmpty()
    {
      foreach (PermissionDescriptor descriptor in descriptors)
      {
        if (descriptor.name == null || !descriptor.name.Equals(string.Empty) || descriptor.operation == PermissionDescriptor.defaultOperation ||
          descriptor.scopeName == null || !descriptor.scopeName.Equals(PermissionDescriptor.defaultScopeName) ||
          descriptor.objectName == null || !descriptor.objectName.Equals(PermissionDescriptor.defaultObjectName))
        {
          return false;
        }
      }
      return true;
    }

    /// <summary>
    /// Adds the class name to the XML encoding of the current permission.
    /// </summary>
    /// <param name="element">The XML encoding of the permission.</param>
    /// <param name="type">Type of the current class.</param>
    private static void AddClassAttribute(SecurityElement element, Type type)
    {
      string typename = type.FullName;
      element.AddAttribute("class", String.Format("{0}, {1}", typename, type.Module.Assembly.FullName.Replace('"', '\'')));
    }

    /// <summary>
    /// Checks if the current permission and the permission specified in the <paramref name="perm" /> parameter are of the same type.
    /// </summary>
    /// <param name="perm">The permission to check for it's type.</param>
    /// <returns>true, if the current permission and the specified permission are of the same type; otherwise, false.</returns>
    private bool VerifyType(IPermission perm)
    {
      return perm != null && !(perm.GetType() != base.GetType());
    }

    /// <summary>
    /// Initializies the permission with the given AzMan operation description.
    /// </summary>
    /// <param name="name">The name of the identity associated with the current principal.</param>
    /// <param name="operation">The AzMan operation number.</param>
    /// <param name="scopeName">The name of a scope as defined in the AzMan store.</param>
    /// <param name="objectName">The string used for AzMan run-time auditing.</param>
    private void SetParams(string name, int operation = PermissionDescriptor.defaultOperation, string scopeName = PermissionDescriptor.defaultScopeName, string objectName = PermissionDescriptor.defaultObjectName)
    {
      descriptors = new PermissionDescriptor[1];
      descriptors[0] = new PermissionDescriptor();
      descriptors[0].name = name;
      descriptors[0].operation = operation;
      descriptors[0].scopeName = scopeName;
      descriptors[0].objectName = objectName;
    }

    #endregion Internal and private helper methods
  }
}